﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Super_Pong.Classes
{
    public abstract class Entity : GameObject
    {
        #region Atributes
        float speed;
        private BoundingBox collisionBox;
        private Vector2 position;
        private Vector2 direction = Vector2.Zero;
        private Animation animation;
        #endregion

        #region Methods

        #region StandartCollisionCode
        /// <summary>
        /// Updates the BB position and size
        /// </summary>
        private void updateBoundingBox()
        {
            this.collisionBox.Min.X = position.X;
            this.collisionBox.Min.Y = position.Y;
            this.collisionBox.Max.X = position.X + animation.GetCurrentImage().Bounds.Width;
            this.collisionBox.Max.X = position.Y + animation.GetCurrentImage().Bounds.Height;
        }

        /// <summary>
        /// Check for collision with the passed BB. Automatically update this entity BB before the check.
        /// </summary>
        /// <param name="bb">The target BB to check intersection with this entity BB.</param>
        /// <returns>True if theres a collision between this BB and the passed one.</returns>
        public bool isColliding(BoundingBox bb)
        {
            //Updates this entity BB before the collision check
            updateBoundingBox();

            //Check for collision with the input bounding box
            if (this.collisionBox.Intersects(bb))
                return true;

            //No collision
            return false;
        }

        /// <summary>
        /// Check for collision with the passed Entity.
        /// Automatically update this entity BB before the check.
        /// Automatically update the passed entity BB before the check.
        /// </summary>
        /// <param name="bb">The target BB to check intersection with this entity BB.</param>
        /// <returns>True if theres a collision between this BB and the passed one.</returns>
        public bool isColliding(Entity e)
        {
            //Updates this entity BB before the collision check
            updateBoundingBox();
            e.updateBoundingBox();

            //Check for collision with the input bounding box
            if (this.collisionBox.Intersects(e.getBoundingBox()))
                return true;

            //No collision
            return false;
        }

        /// <summary>
        /// The default collision response, automatically called if this entity is in the collision manager.
        /// </summary>
        /// <param name="e">The entity which this one is colliding to.</param>
        public abstract void collide(Entity e);
        #endregion

        #region Move
        /// <summary>
        /// Move the entity.
        /// </summary>
        /// <param name="move">The vector to add to the position.</param>
        public void move(Vector2 move)
        {
            position += move;
        }

        /// <summary>
        /// Move the entity.
        /// </summary>
        /// <param name="dir">The direction to go.</param>
        /// <param name="velocity">The velocity to follow.</param>
        public void move(Vector2 dir, float velocity)
        {
            position += dir*velocity;
        }

        /// <summary>
        /// Move the entity.
        /// </summary>
        /// <param name="x">The amount to move in x axis.</param>
        /// <param name="y">The amount to move in y axis.</param>
        public void move(float x, float y)
        {
            position += new Vector2(x,y);
        }
        #endregion

        #region Vector Modifiers
        /// <summary>
        /// Accelerate the entity.
        /// </summary>
        /// <param name="amount">The acceleration vector that will be added to the velocity.</param>
        public void accelerate(Vector2 amount)
        {
            //Create a vector to sum
            Vector2 velocityVector = direction * speed;
            //Sum the aceleration
            velocityVector += amount;
            //Break the vector again
            //Receive the speed
            speed = velocityVector.Length();
            //Receive the direction
            velocityVector.Normalize();
            direction = velocityVector;
        }

        /// <summary>
        /// Accelerate the entity.
        /// </summary>
        /// <param name="dir">The direction to accelerate.</param>
        /// <param name="amount">The "force" of acceleration.</param>
        public void accelerate(Vector2 dir, float amount)
        {
            //Create a vector to sum
            Vector2 velocityVector = direction * speed;
            //Sum the aceleration
            velocityVector += dir * amount;
            //Break the vector again
            //Receive the speed
            speed = velocityVector.Length();
            //Receive the direction
            velocityVector.Normalize();
            direction = velocityVector;
        }
        #endregion

        #region Sets
        /// <summary>
        /// Set this entity heading direction.
        /// </summary>
        /// <param name="dir">The vector containing the direction.</param>
        public void setDirection(Vector2 dir)
        {
            direction = dir;
        }
        
        /// <summary>
        /// Overload to the setDirection using float (x,y) coordinates.
        /// </summary>
        /// <param name="dirX">The X coordinate of the direction.</param>
        /// <param name="dirY">The Y coordinate of the direction.</param>
        public void setDirection(float dirX, float dirY)
        {
            direction.X = dirX;
            direction.Y = dirY;
        }
        
        /// <summary>
        /// Set this entity position.
        /// </summary>
        /// <param name="pos">The vector containing the new position.</param>
        public void setPosition(Vector2 pos)
        {
            position = pos;
        }

        /// <summary>
        /// Set this entity position.
        /// </summary>
        /// <param name="posX">The X value to move this entity to.</param>
        /// <param name="posY">The Y value to move this entity to.</param>
        public void setPosition(float posX, float posY)
        {
            position.X = posX;
            position.Y = posY;
        }

        /// <summary>
        /// Set this entity speed. This speed can be used to apply movment to
        /// this entity in the subclasses multiplying the direction by it. Do
        /// this in the update method. Also remember to multiply by the elapsed
        /// time.
        /// </summary>
        /// <param name="velocity">The speed or velocity data to store.</param>
        public void setSpeed(float velocity)
        {
            speed = velocity;
        }
        
        /// <summary>
        /// Set this entity animation. Its "face". Use the getCurrentImage()
        /// from the Animation class to extract a Texture2D drawable image
        /// and display it on the SpriteBatch passed to the draw method.
        /// </summary>
        /// <param name="ani">An animation.</param>
        public void setAnimation(Animation ani)
        {
            if(ani != null && ani != this.animation)
                animation = ani;
        }
        #endregion

        #region Gets
        public Texture2D getImage()
        {
            return getAnimation().GetCurrentImage();
        }

        public Vector2 getDirection()
        {
            return direction;
        }

        public float getXDirection()
        {
            return direction.X;
        }

        public float getYDirection()
        {
            return direction.Y;
        }

        public Vector2 getPosition()
        {
            return position;
        }

        public float getSpeed()
        {
            return speed;
        }

        public int getWidth()
        {
            return animation.GetCurrentImage().Bounds.Width;
        }

        public int getHeight()
        {
            return animation.GetCurrentImage().Bounds.Height;
        }

        public Animation getAnimation()
        {
            return animation;
        }

        public BoundingBox getBoundingBox()
        {
            return collisionBox;
        }
        #endregion

        #region Utils
        /// <summary>
        /// Grab the direction between this entity and the target.
        /// </summary>
        /// <param name="target">The target vector to grab the direction.</param>
        /// <returns>The direction vector.</returns>
        public Vector2 getDirectionTo(Vector2 target)
        {
            return Utils2D.getDirectionTo(position, target);
        }
        
        /// <summary>
        /// Get the direction between this entity and another.
        /// </summary>
        /// <param name="e">The entity to target.</param>
        /// <returns>The direction vector.</returns>
        public Vector2 getDirectionTo(Entity e)
        {
            return getDirectionTo(e.getPosition());
        }

        /// <summary>
        /// Get the distance between a target vector and this entity.
        /// </summary>
        /// <param name="target">The target vector to calculate the distance to.</param>
        /// <returns>The distance between this entity and the target vector.</returns>
        public float getDistanceTo(Vector2 target)
        {
            return Utils2D.getDistanceTo(target, position);
        }

        /// <summary>
        /// Get the distance between an target entity and this entity.
        /// </summary>
        /// <param name="target">The target entity to calculate the distance to.</param>
        /// <returns>The distance between this entity and the target one.</returns>
        public float getDistanceTo(Entity target)
        {
            return getDistanceTo(target.getPosition());
        }
        #endregion

        #endregion

    }
}
